package com.asterisk;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObject;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;
import java.util.Random;
import java.util.Map.Entry;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.Set;

import org.asteriskjava.manager.AuthenticationFailedException;
import org.asteriskjava.manager.ManagerConnection;
import org.asteriskjava.manager.ManagerConnectionFactory;
import org.asteriskjava.manager.ManagerEventListener;
import org.asteriskjava.manager.TimeoutException;
import org.asteriskjava.manager.action.HangupAction;
import org.asteriskjava.manager.action.OriginateAction;
import org.asteriskjava.manager.action.SetVarAction;
import org.asteriskjava.manager.action.StatusAction;
import org.asteriskjava.manager.event.BridgeEvent;
import org.asteriskjava.manager.event.ExtensionStatusEvent;
import org.asteriskjava.manager.event.HangupEvent;
import org.asteriskjava.manager.event.ManagerEvent;
import org.asteriskjava.manager.event.NewChannelEvent;
import org.asteriskjava.manager.event.NewStateEvent;
import org.asteriskjava.manager.event.PeerStatusEvent;
import org.asteriskjava.manager.response.ManagerResponse;

import com.Main.MyServiceInterface;
import com.protocols.GeneralProtocolData;
import com.protocols.ProtoOperations;
import com.protocols.sctp.TreatInCommingTcp;
import com.protocols.sctp.m3ua.M3uaHandlerInterface;
import com.protocols.sctp.m3ua.sccp.SccpHandler;
import com.protocols.sctp.m3ua.sccp.tcap.TcapHandlerInterface;
import com.protocols.sctp.m3ua.sccp.tcap.camel.BCSMEvent;
import com.protocols.sctp.m3ua.sccp.tcap.camel.CamelApplyChargingReport;
import com.protocols.sctp.m3ua.sccp.tcap.camel.CamelEventReportBCSM;
import com.protocols.sctp.m3ua.sccp.tcap.camel.CamelHandlerInterface;
import com.protocols.sctp.m3ua.sccp.tcap.camel.CamelRequestReportBCSMEvent;
import com.protocols.sctp.m3ua.sccp.tcap.camel.CamelSessionInfo;

public class AmiManager extends java.rmi.server.UnicastRemoteObject implements AmiManagerInterface ,MyServiceInterface ,ManagerEventListener
{
    private ManagerConnection managerConnection;   
    
    public  HashMap<String,ChannelInfo> ChannelInformation = new HashMap<String,ChannelInfo>();
    private Registry Registry;  
    private CamelHandlerInterface CamelHandler;
    Logger Log = Logger.getLogger(this.getClass().getName());
    Boolean logInfoEnabled=false;
    
    
    
    // Default Configuration
    private Boolean amiStaus=false;
    int layerServiceport=9001;
	String camelhandlerurl= "CamelHandler";
	String amiPeerIp=	"localhost";
	int amiPeerPort=2906;		
	String amimanagerurl= "AmiManager";
	int ConnRetry=3000;		
	String logfile= "var/log/ami.log";
	Level loglevel= Level.OFF;
	String amiusername="";	
	String amipassword=	"";	
	
    
    public AmiManager()  throws RemoteException 
    {
    	super();      
    }


    public void initialise() throws Exception{	
		
		Properties properties=null;
		try{			
			properties = new java.util.Properties();
			properties.load( new FileInputStream("General.conf"));
		}catch( IOException e){
			System.out.println("Couldn't locate the Config File");				
			System.exit(0);
		}			
		logfile= properties.getProperty("ami.log.filepath");
		loglevel= Level.parse(properties.getProperty("ami.log.level"));
		
		FileHandler filehandler=null;
		try{
			filehandler= new FileHandler(logfile,true);
			filehandler.setFormatter(new SimpleFormatter());
			Log.addHandler(filehandler);
			Log.setLevel(loglevel);				
		}catch( IOException e){
			System.out.println("Issue Opening the Log File. Setting the Log Level to 'OFF'");	
			Log.setLevel(Level.OFF);	
		}
		
		logInfoEnabled=Log.isLoggable(Level.INFO);	
		
		layerServiceport=Integer.valueOf(properties.getProperty("layerservice.port"));
		amimanagerurl= properties.getProperty("ami.layerservice.url");
		amiPeerIp=	properties.getProperty("ami.peer.ip");
		amiusername=	properties.getProperty("ami.peer.username");	
		amipassword=	properties.getProperty("ami.peer.password");	
		camelhandlerurl= properties.getProperty("camel.layerservice.url");
		ConnRetry=Integer.valueOf(properties.getProperty("connectionretryinterval"));		
		amiStaus=properties.getProperty("ami.status").equals("on");
			
		try{
			Registry = LocateRegistry.createRegistry( layerServiceport );
			if(logInfoEnabled)Log.info("Created Registry on Port:"+layerServiceport);
		}catch(RemoteException e){
			Registry = LocateRegistry.getRegistry( layerServiceport );	
			if(logInfoEnabled)Log.info("Obtained Registry from Port:"+layerServiceport);
		}
			
			
		Registry.rebind(amimanagerurl, this);			
		if(logInfoEnabled)Log.info("Bound AmiManager Handler on Url:"+amimanagerurl);
		
		
	}

    public void Start(){
    	try{
    		while(true){
            	synchronized (this.ChannelInformation) {
    				Set<Entry<String,ChannelInfo>> charginginfoset=this.ChannelInformation.entrySet();
    				for (Entry e:charginginfoset){
    					ChannelInfo channelInfo = (ChannelInfo)e.getValue();
    					if (channelInfo.getAnswered()&&channelInfo.applyCharging()&&(new Date().getTime() - channelInfo.getStartTime()>= channelInfo.getTimeLeft())){
							
    						if(logInfoEnabled)Log.info("Channel: "+(String)e.getKey()+"End of Time Given by Apply Charging");
							hangUpCall((String)e.getKey());							
							long timeleft=channelInfo.getTimeLeft() -((new Date()).getTime()-channelInfo.getStartTime());
							CamelApplyChargingReport camelApplyChargingReport = new CamelApplyChargingReport();
							camelApplyChargingReport.setTimeIfNoTariffSwitch((timeleft>=0)?timeleft:0);
							camelApplyChargingReport.setLegActive(false);
							camelApplyChargingReport.setPartyToCharge(1);
							if(logInfoEnabled)Log.info("Channel: "+(String)e.getKey()+"Sending Apply Charging Report");
							CamelHandler.sendApplyChargingReport((String)e.getKey(), camelApplyChargingReport);
						
						}
    				}
    			}
    			
    			
            	 Thread.sleep(1000);
            	 
            }  
	    }catch(Exception e){
			
		}
	  
    }
    
    
    private void establishAmiConnection() throws Exception
    {
    	Boolean retry=true;
		while(retry)
		{
			try{
				ManagerConnectionFactory factory = new ManagerConnectionFactory(amiPeerIp, amiusername, amipassword);
		        managerConnection = factory.createManagerConnection();
				retry=false;
			}catch(Exception e){	
				Log.severe("Couldn't Establish the  Connection to Sip Server "+amiPeerIp+". Retry in "+ConnRetry+" Secs.");
				Thread.sleep(ConnRetry);
			}
		}
		if(logInfoEnabled)Log.info("Establish the  Connection to Sip Server  "+amiPeerIp);
		 managerConnection.login();
	     managerConnection.addEventListener(this); 
    }
    
    
    private void establishCamelConnection() throws Exception
    {
	    Boolean retry=true;
		while(retry)
		{
			try{
				CamelHandler=(CamelHandlerInterface)(Registry.lookup(camelhandlerurl));
				retry=false;
			}catch(Exception e){	
				Log.severe("Couldn't Obtained the CamelHandler Instance. Retry in "+ConnRetry+" Secs.");
				Thread.sleep(ConnRetry);
			}
		}				
		if(logInfoEnabled)Log.info("Obtained CamelHandler Instance");
		
    }
    
  

	@Override
	public void onManagerEvent(ManagerEvent event) {
		try{
			if (CamelHandler==null){
				Registry registry=LocateRegistry.getRegistry(9001);	
				CamelHandler=(CamelHandlerInterface)(registry.lookup("CamelHandler"));
			}			
			String eventname=event.getClass().getSimpleName();
			
			switch (eventname) {
			case "HangupEvent":	// Call
			{
				if(logInfoEnabled)Log.info("Hangup Event Received"+event.toString());
				
				HangupEvent hangup= (HangupEvent)event;	
				int hangupcause = hangup.getCause();
																				
				ChannelInfo channelInfo=null;
				synchronized (ChannelInformation) {
					channelInfo=ChannelInformation.remove(hangup.getChannel());
				}
				
				if (!(channelInfo==null)){
					
					CamelEventReportBCSM Disconnect = new CamelEventReportBCSM();
					if(hangupcause==17 ||hangupcause==21||hangupcause==20)	// Busy, Reject, Sub Absent	-ok
					{
						Disconnect.setLegId(2);
						Disconnect.setMissCallInfo(1);
						Disconnect.setEventSpecificInformationBCSM(5, 0, 0, hangupcause);
					}
					else if(hangupcause==19)	// No Answer  - ok
					{
						Disconnect.setEventTypeBCSM(6);
						Disconnect.setLegId(2);
						Disconnect.setMissCallInfo(1);
						
					}
					else 
					{
						if (channelInfo.getAnswered()){
							Disconnect.setLegId(1);	//oDisconnect
							Disconnect.setMissCallInfo(0);
							Disconnect.setEventSpecificInformationBCSM(9, 0, 0, hangupcause);
						}
						else
						{
							Disconnect.setEventTypeBCSM(10);		// Originator abandon	-ok
							Disconnect.setLegId(1);
							Disconnect.setMissCallInfo(1);
							
						}
						
					}
					if(logInfoEnabled)Log.info("Sending BCSM Event Report. Hangup Cause:"+hangupcause);
					CamelHandler.sendEventReportBCSM(hangup.getChannel(), Disconnect);	
						
					if(channelInfo.applyCharging())
					{
						long timeleft=0;
						if(channelInfo.getAnswered()){
							timeleft=channelInfo.getTimeLeft() -((new Date()).getTime()-channelInfo.getStartTime());
						}
						else
						{
							timeleft=channelInfo.getTimeLeft() ;
						}
						CamelApplyChargingReport camelApplyChargingReport = new CamelApplyChargingReport();
						camelApplyChargingReport.setTimeIfNoTariffSwitch((timeleft>=0)?timeleft:0);
						camelApplyChargingReport.setLegActive(false);
						camelApplyChargingReport.setPartyToCharge(1);
						if(logInfoEnabled)Log.info("Sending Apply Charging Report");
						CamelHandler.sendApplyChargingReport(hangup.getChannel(), camelApplyChargingReport);
					}
					
				}
				
				
				break;
			}
			case "PeerStatusEvent":	//system
			{		
				if(logInfoEnabled)Log.info("PeerStatusEvent Received"+event.toString());
				PeerStatusEvent peerStatusEvent= (PeerStatusEvent)event;
				//peerStatusEvent.getPeerStatus()	"Registered, Reachable" and "Unregistered"
				break;
			}
			case "NewStateEvent":	//NewStateEvent of the Call, Ring(MO), Ringing(MT), UP(answered)	; Call
			{	
				NewStateEvent newStateEvent= (NewStateEvent)event;
				if(logInfoEnabled)Log.info("Channel: "+newStateEvent.getChannel()+ " NewStateEvent Received"+event.toString());	
				
				if (newStateEvent.getChannelState()==4){	//Ring (MO)
				
					synchronized (ChannelInformation) {
						ChannelInfo channelinfo=ChannelInformation.get(newStateEvent.getChannel());
						if(!(channelinfo==null)) channelinfo.setChannelTypeOriginating(true);
					}
				}
				else if (newStateEvent.getChannelState()==5){	//Ringing (MT)
					synchronized (ChannelInformation) {
						ChannelInfo channelinfo=ChannelInformation.get(newStateEvent.getChannel());
						if(!(channelinfo==null)) channelinfo.setChannelTypeOriginating(false);						
					}
				}
				else if (newStateEvent.getChannelState()==6){	//UP (MO,MT)	// When the B Party answer 
												
					CamelEventReportBCSM Answer = new CamelEventReportBCSM();
					
					Answer.setLegId(2);
					Answer.setMissCallInfo(1);
					
					ChannelInfo channelinfo=null;					
					synchronized (ChannelInformation) {
						channelinfo=ChannelInformation.get(newStateEvent.getChannel());
						if (!(channelinfo==null)){
							channelinfo.setAnswered(true);
							channelinfo.setStartTime(new Date().getTime());
						}
					}
					
					if (!(channelinfo==null)){
						if(channelinfo.isChannelTypeOriginating())
						{
							Answer.setEventTypeBCSM(7);	//oAnswer
							if(logInfoEnabled)Log.info("Channel: "+newStateEvent.getChannel()+ " Sent Event Report BCSM-Answer");	
							CamelHandler.sendEventReportBCSM(newStateEvent.getChannel(), Answer);
						}
						else
						{
							Answer.setEventTypeBCSM(15);	//tAnswer
							if(logInfoEnabled)Log.info("Channel: "+newStateEvent.getChannel()+ " Sent Event Report BCSM-Answer");
							CamelHandler.sendEventReportBCSM(newStateEvent.getChannel(), Answer);
						}
							
					}
					
								
				}
				
				break;
			}
			default:
			{
				
				break;
			}
			}
		
		}catch(Exception e){
			Log.severe(":Exception Occured :" +e.getClass().getSimpleName()+":"+ Arrays.toString(e.getStackTrace()));
		}
		
	}

	@Override
	public void hangUpCall(String channelID) throws RemoteException {
	
		try
		{
			if(logInfoEnabled)Log.info("Channel: "+channelID+ " Going to Hangup");
			// Generates Hangup Events as well
			ChannelInfo thischannelinfo=null;
			synchronized (ChannelInformation) {
				thischannelinfo=ChannelInformation.remove(channelID);
				
			}
			if (!(thischannelinfo==null))
			{
				HangupAction hangupaction= new  HangupAction();
				hangupaction.setChannel(channelID);
				hangupaction.setCause(31);
				if(logInfoEnabled)Log.info("Channel: "+channelID+ " Sending Hangup to the AMI");
				ManagerResponse originateResponse=managerConnection.sendAction(hangupaction,3000);
			}
		}catch(Exception e){
			Log.severe(":Exception Occured :" +e.getClass().getSimpleName()+":"+ Arrays.toString(e.getStackTrace()));
			
		}
		
	}
	
	@Override
	public Boolean activityTest(String channelID) throws RemoteException {
		
		if(logInfoEnabled)Log.info("Channel: "+channelID+ " Received Activity Test");
		
		StatusAction statusAction= new  StatusAction();
		statusAction.setChannel(channelID);
		
		
		try
		{
			ManagerResponse resp= managerConnection.sendAction(statusAction, 3000);
			Boolean chanelstat=resp.getResponse().equals("Success")?true:false;
			if(logInfoEnabled)Log.info("Channel: "+channelID+ " Chanel Status :"+chanelstat);
			return chanelstat ;
		}catch(Exception e){
			Log.severe(":Exception Occured :" +e.getClass().getSimpleName()+":"+ Arrays.toString(e.getStackTrace()));
			
		}
		
		return false;
	}
	



	@Override
	public void addBCSMEvents(String channelID,CamelRequestReportBCSMEvent bcsmEvents) throws RemoteException {
		
		if(logInfoEnabled)Log.info("Channel: "+channelID+ " Received Request Report BCSM Events");
		synchronized (ChannelInformation) {
			ChannelInformation.put(channelID, new ChannelInfo(bcsmEvents));
		}
		
		
	}



	@Override
	public void applyCharging(String channelID, long maxcallduration,Boolean releaseifdurationexceed, int partytocharge)	throws RemoteException {
		if(logInfoEnabled)Log.info("Channel: "+channelID+ " Received Apply Charging");
		synchronized (ChannelInformation) {
			ChannelInfo chanelinfo=ChannelInformation.get(channelID);
			if(!(chanelinfo==null))
			{
				chanelinfo.setApplyChargingInfo(maxcallduration, releaseifdurationexceed, partytocharge);
				if(chanelinfo.getAnswered()){
					chanelinfo.setStartTime(new Date().getTime());
				}
			}
		}
			
			
	}


	@Override
	public void startLayer() throws Exception {
		initialise();
		if (amiStaus){
			establishCamelConnection();
			establishAmiConnection();
	    	Start();
		}
		
	}
	
	
	public static void main(String args[]) throws Exception {
		AmiManager ami = new AmiManager();
		ami.initialise();
		if (ami.amiStaus){
			//ami.establishCamelConnection();
			ami.establishAmiConnection();
			ami.Start();
			

		}
		
	}
	
}